﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;

namespace CustomConsole
{
    [StructLayout(LayoutKind.Sequential)]
    public struct ConsoleParams
    {
        // startup stuff
        public int dwConsoleMainThreadId;
        public int dwParentProcessId;
        public int dwNotificationTimeout;
        public int dwRefreshInterval;
        public int dwRows;
        public int dwColumns;
        public int dwBufferRows;
        public int dwBufferColumns;

        // stuff set by console hook
        public int dwMaxRows;
        public int dwMaxColumns;
        public IntPtr hwndConsoleWindow;
    }

    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    public delegate void ConsoleChangeCallback(bool resize);

    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    public delegate void ConsoleCloseCallback();

    public class NativeConsoleHost : IDisposable
    {

        [DllImport("ConsoleHost.dll",
             EntryPoint = "?CreateConsoleHost@@YAPAVConsoleHost@@XZ")]
        private static extern IntPtr CreateConsoleHost();

        [DllImport("ConsoleHost.dll",
             EntryPoint = "?DeleteConsoleHost@@YAXPAVConsoleHost@@@Z")]
        private static extern void DeleteConsoleHost(IntPtr ths);

        [DllImport("ConsoleHost.dll",
             EntryPoint = "?StartShellProcess@ConsoleHost@@QAE_NPBG00KK@Z",
             CallingConvention = CallingConvention.ThisCall,
             CharSet = CharSet.Unicode)]
        private static extern int StartShellProcess(IntPtr ths, string shell, string initDir, string consoleTitle, int startupRows, int startupCols);


        [DllImport("ConsoleHost.dll",
             EntryPoint = "?SetupDelegates@ConsoleHost@@QAEXP6AX_N@ZP6AXXZ@Z",
             CallingConvention = CallingConvention.ThisCall,
             CharSet = CharSet.Unicode)]
        private static extern int _SetupDelegate(IntPtr ths, ConsoleChangeCallback onchange, ConsoleCloseCallback onclose);

        [DllImport("ConsoleHost.dll",
             EntryPoint = "?StartMonitorThread@ConsoleHost@@QAEKXZ",
             CallingConvention = CallingConvention.ThisCall)]
        private static extern int StartMonitorThread(IntPtr ths);


        [DllImport("ConsoleHost.dll",
             EntryPoint = "?GetConsoleParams@ConsoleHost@@QAEPAUConsoleParams@@XZ",
             CallingConvention = CallingConvention.ThisCall)]
        private static extern IntPtr _GetConsoleParams(IntPtr ths);

        [DllImport("ConsoleHost.dll",
             EntryPoint = "?GetConsoleBuffer@ConsoleHost@@QAEPAU_CHAR_INFO@@XZ",
             CallingConvention = CallingConvention.ThisCall)]
        private static extern IntPtr _GetConsoleBuffer(IntPtr ths);

        [DllImport("ConsoleHost.dll",
             EntryPoint = "?GetConsoleSize@ConsoleHost@@QAEXPAH0@Z",
             CallingConvention = CallingConvention.ThisCall)]
        private static extern void _GetConsoleSize(IntPtr ths, out int cols, out int rows);

        [DllImport("ConsoleHost.dll",
             EntryPoint = "?SetConsoleSize@ConsoleHost@@QAEXHH@Z",
             CallingConvention = CallingConvention.ThisCall)]
        private static extern void _SetConsoleSize(IntPtr ths, int cols, int rows);


        [DllImport("ConsoleHost.dll",
             EntryPoint = "?GetConsoleInfo@ConsoleHost@@QAEPAU_CONSOLE_SCREEN_BUFFER_INFO@@XZ",
             CallingConvention = CallingConvention.ThisCall)]
        private static extern IntPtr _GetConsoleInfo(IntPtr ths);

        [DllImport("ConsoleHost.dll",
             EntryPoint = "?SetScrollPosition@ConsoleHost@@QAEXHH@Z",
             CallingConvention = CallingConvention.ThisCall)]
        private static extern void _SetScrollPosition(IntPtr ths, int cols, int rows);

        [DllImport("ConsoleHost.dll",
             EntryPoint = "?SendTextToConsole@ConsoleHost@@QAEXPBG@Z",
             CallingConvention = CallingConvention.ThisCall,
             CharSet = CharSet.Unicode)]
        private static extern void _SendTextToConsole(IntPtr ths, string text);

        [return: MarshalAs(UnmanagedType.Bool)]
        [DllImport("user32.dll", EntryPoint = "PostMessage", SetLastError = true)]
        private static extern bool _PostMessage(IntPtr hWnd, int Msg, int wParam, int lParam);

        IntPtr _host;

        ConsoleChangeCallback _consoleChange;
        ConsoleCloseCallback _consoleClose;

        public NativeConsoleHost()
        {
            _host = CreateConsoleHost();
            _consoleChange = new ConsoleChangeCallback(OnChange);
            _consoleClose = new ConsoleCloseCallback(OnClose);
            _SetupDelegate(_host, _consoleChange, _consoleClose);
        }

        public int StartShell(string shell, string initDir, string consoleTitle, int startupRows, int startupCols)
        {
            return StartShellProcess(_host, shell, initDir, consoleTitle, startupRows, startupCols);
        }

        public int StartMonitor()
        {
            return StartMonitorThread(_host);
        }


        public event ConsoleChangeCallback ConsoleChange;
        public event ConsoleCloseCallback ConsoleClose;

        private void OnChange(bool resize)
        {
            if (ConsoleChange != null)
            {
                ConsoleChange(resize);
            }
        }

        private void OnClose()
        {
            if (ConsoleClose != null)
            {
                ConsoleClose();
            }
        }

        public ConsoleParams GetConsoleParams()
        {
            IntPtr ptr = _GetConsoleParams(_host);
            return (ConsoleParams)Marshal.PtrToStructure(ptr, typeof(ConsoleParams));
        }


        public CHAR_INFO[] GetConsoleBuffer(int size)
        {
            CHAR_INFO[] ret = new CHAR_INFO[size];
            IntPtr ptr = _GetConsoleBuffer(_host);
            for (int i = 0; i < size; i++)
            {
                ret[i] = (CHAR_INFO)Marshal.PtrToStructure(ptr, typeof(CHAR_INFO));
                ptr = (IntPtr)((int)ptr + Marshal.SizeOf(ret[i]));
            }

            return ret;
        }

        public void SetConsoleSize(int cols, int rows)
        {
            _SetConsoleSize(_host, cols, rows);
        }

        public void SetScrollPosition(int cols, int rows)
        {
            _SetScrollPosition(_host, cols, rows);
        }

        public int Columns
        {
            get
            {
                int col; int row;
                _GetConsoleSize(_host, out col, out row);
                return col;
            }
        }

        public int Rows
        {
            get
            {
                int col; int row;
                _GetConsoleSize(_host, out col, out row);
                return col;
            }
        }
        public bool PostMessage(IntPtr hWnd, int Msg, int wParam, int lParam)
        {
            return _PostMessage(hWnd, Msg, wParam, lParam);
        }

        public CONSOLE_SCREEN_BUFFER_INFO GetConsoleInfo()
        {
            IntPtr ptr = _GetConsoleInfo(_host);
            return (CONSOLE_SCREEN_BUFFER_INFO)Marshal.PtrToStructure(ptr, typeof(CONSOLE_SCREEN_BUFFER_INFO));
        }

        public void SendTextToConsole(string text)
        {
            _SendTextToConsole(_host, text);
        }

        #region IDisposable メンバ

        public void Dispose()
        {
            if(_host != IntPtr.Zero)
                DeleteConsoleHost(_host);
        }

        #endregion
    }

    [Flags()]
    public enum CharAttributes : short
    {
        ForegroundBlue = 0x0001,
        ForegroundGreen = 0x0002,
        ForegroundRed = 0x0004,
        ForegroundIntensity = 0x0008,
        BackgroundBlue = 0x0010,
        BackgroundGreen = 0x0020,
        BackgroundRed = 0x0040,
        BackgroundIntensity = 0x0080,
    }

    [StructLayout(LayoutKind.Explicit)]
    public struct CHAR
    {
        [FieldOffset(0)]
        public byte AsciiChar;
        [FieldOffset(0)]
        public short UnicodeChar;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct CHAR_INFO
    {
        public CHAR Char;
        public CharAttributes Attributes;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct COORD
    {
        public short X;
        public short Y;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct SMALL_RECT
    {
        public short Left;
        public short Top;
        public short Right;
        public short Bottom;
    } 

    [StructLayout(LayoutKind.Sequential)]
    public struct CONSOLE_SCREEN_BUFFER_INFO
    {
        public COORD dwSize;
        public COORD dwCursorPosition;
        public CharAttributes wAttributes;
        public SMALL_RECT srWindow;
        public COORD dwMaximumWindowSize;
    } 


}
